package cn.ibizlab.util.filter;

import cn.ibizlab.util.domain.IEntity;
import cn.ibizlab.util.helper.BeanCache;
import cn.ibizlab.util.helper.JacksonUtils;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.annotation.Transient;
import org.springframework.data.domain.Sort;
import org.springframework.util.ObjectUtils;
import java.lang.reflect.ParameterizedType;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;

@Slf4j
@Data
public class QueryWrapperContext<T extends IEntity> extends SearchContextBase implements ISearchContext{

    @JsonIgnore
    @JSONField(serialize = false)
    @Transient
    private BeanCache.BeanSchema beanSchema;

    @JsonIgnore
    @JSONField(serialize = false)
    public BeanCache.BeanSchema getBeanSchema() {
        if(beanSchema == null) {
            ParameterizedType parameterizedType = (ParameterizedType) getClass().getGenericSuperclass();
            Class<T> type = (Class<T>)parameterizedType.getActualTypeArguments()[0];
            beanSchema = BeanCache.BeanSchema.from(type);
        }
        return beanSchema;
    }

    @JsonIgnore
    @JSONField(serialize = false)
    @Transient
    public QueryWrapper<T> selectCond;

    /**
     * 解析查询上下文中的参数，构建mybatis-plus分页对象
     * @return
     */
    @JsonIgnore
    @JSONField(serialize = false)
    @Transient
    public Page<T> getPages(){
        Page<T> page;
        List<OrderItem> orderfieldList = new ArrayList<>();

        int currentPage=getPageable().getPageNumber();
        int pageSize=getPageable().getPageSize();

        //构造mybatis-plus分页
        if(ObjectUtils.isEmpty(currentPage) || ObjectUtils.isEmpty(pageSize))
            page=new Page<>(1,Short.MAX_VALUE);
        else
            page=new Page<>(currentPage+1,pageSize);
        page.offset(this.getOffset());
        //构造mybatis-plus排序
        Sort sort = getPageable().getSort();

        if(!ObjectUtils.isEmpty(sort)) {
            sort.stream().forEach(sort_order ->{
                if(sort_order.getDirection()== Sort.Direction.ASC)
                    orderfieldList.add(new OrderItem(getBeanSchema().translateFieldName(sort_order.getProperty()),true));
                else if(sort_order.getDirection()== Sort.Direction.DESC)
                    orderfieldList.add(new OrderItem(getBeanSchema().translateFieldName(sort_order.getProperty()),false));
            });
        }

        if(orderfieldList.size()>0)
            page.setOrders(orderfieldList);

        return page;
    }

    @JsonIgnore
    @JSONField(serialize = false)
    @Transient
    public QueryWrapper<T> getSelectCond() {
        if(selectCond == null) {
            Class<T> entityClass = (Class<T>)((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
            context = ignoreUnknownField(JacksonUtils.toObj(this,LinkedHashMap.class));
            selectCond = new QueryWrapper<>();
            QueryFilter filter = getFilter();
            context.keySet().stream().filter(e->e instanceof String && ((String)e).startsWith("n_")).findAny().ifPresent(e->filter.parse(context));

            if (!ObjectUtils.isEmpty(filter)) {
                Consumer<QueryWrapper<T>> queryWrapper = parseQueryFilter(filter);
                if (!ObjectUtils.isEmpty(queryWrapper))
                    selectCond.and(queryWrapper);
            }
        }
        return selectCond;
    }

    @JsonIgnore
    @JSONField(serialize = false)
    @Transient
    private Map<String, Object> context = getParams();

    /**
     * 获取数据上下文
     *
     * @return
     */
    @JsonIgnore
    @JSONField(serialize = false)
    @Transient
    public Map<String, Object> getDatacontext() {
        return context;
    }

    /**
     * 获取网页请求上下文
     *
     * @return
     */
    @JsonIgnore
    @JSONField(serialize = false)
    @Transient
    public Map<String, Object> getWebcontext() {
        return context;
    }


    /**
     * 解析自定义查询条件
     * @param queryFilter
     * @return
     */
    private Consumer<QueryWrapper<T>> parseQueryFilter(QueryFilter queryFilter){
        if(queryFilter.any().size()==0  && queryFilter.get$or()==null && queryFilter.get$and()==null && ObjectUtils.isEmpty(customCond)) {
            return null;
        }
        Consumer<QueryWrapper<T>> consumer = queryWrapper -> {
            Consumer fieldConsumer=parseFieldMap(queryFilter.any());
            Consumer orConsumer=parseOrQueryFilter(queryFilter.get$or());
            Consumer andConsumer=parseAndQueryFilter(queryFilter.get$and());
            if(!ObjectUtils.isEmpty(fieldConsumer)){
                queryWrapper.and(fieldConsumer);
            }
            if(!ObjectUtils.isEmpty(orConsumer)){
                queryWrapper.and(orConsumer);
            }
            if(!ObjectUtils.isEmpty(andConsumer)){
                queryWrapper.and(andConsumer);
            }
            if(!ObjectUtils.isEmpty(customCond)){
                queryWrapper.and(query->query.apply(customCond.toString()));
            }
        };
        return consumer;
    }


    /**
     * 解析自定义条件[or]
     * @param queryFilters
     * @return
     */
    private Consumer<QueryWrapper<T>> parseOrQueryFilter(List<QueryFilter> queryFilters) {
        if(queryFilters==null || queryFilters.size()==0)
            return null;
        Consumer<QueryWrapper<T>> consumer = queryWrapper -> {
            for(QueryFilter queryFilter: queryFilters){
                Consumer tempQueryWrapper=parseQueryFilter(queryFilter);
                queryWrapper.or(tempQueryWrapper);
            }
        };
        return consumer;
    }

    /**
     * 解析自定义条件[and]
     * @param queryFilters
     * @return
     */
    private Consumer<QueryWrapper<T>> parseAndQueryFilter(List<QueryFilter> queryFilters) {
        if(queryFilters==null || queryFilters.size()==0) {
            return null;
        }
        Consumer<QueryWrapper<T>> consumer = queryWrapper -> {
            for(QueryFilter queryFilter: queryFilters){
                Consumer tempQueryWrapper=parseQueryFilter(queryFilter);
                queryWrapper.and(tempQueryWrapper);
            }
        };
        return consumer;
    }

    /**
     * 解析自定义条件[字段条件]
     * @param fieldMap
     * @return
     */
    private Consumer<QueryWrapper<T>> parseFieldMap(Map<String , QueryFilter.SegmentCond> fieldMap) {
        if(fieldMap.size()==0) {
            return null;
        }
        Consumer<QueryWrapper<T>> consumer = queryWrapper -> {
            for(Map.Entry<String, QueryFilter.SegmentCond> field: fieldMap.entrySet()){
                String fieldName=getBeanSchema().translateFieldName(field.getKey());
                BeanCache.FieldItem fieldItem = getBeanSchema().getField(fieldName);
                if(!ObjectUtils.isEmpty(fieldItem.getFieldName())) {
                    fieldName = fieldItem.getFieldName();
                    if(DbTypeContextHolder.get().equals(DbType.MYSQL) && !fieldName.startsWith("`"))
                        fieldName = String.format("`%1$s`",fieldName);
                }
                else
                    continue;

                QueryFilter.SegmentCond segmentCond=field.getValue();
                Map<String , Object> segmentCondMap =  segmentCond.any();
                for(Map.Entry<String , Object> fieldCond: segmentCondMap.entrySet()){
                    Object val=fieldCond.getValue();
                    if (val instanceof String && ((String) val).startsWith("$.")&&context!=null)
                        val = context.getOrDefault(((String) val).substring(2),this.getParams().getOrDefault(((String) val).substring(2),null));
                    Object value = val;
                    switch (fieldCond.getKey()){
                        case "$eq":
                            queryWrapper.eq(fieldName,value);
                            break;
                        case "$ne":
                            queryWrapper.ne(fieldName,value);
                            break;
                        case "$gt":
                            queryWrapper.gt(fieldName,value);
                            break;
                        case "$gte":
                            queryWrapper.ge(fieldName,value);
                            break;
                        case "$lt":
                            queryWrapper.lt(fieldName,value);
                            break;
                        case "$lte":
                            queryWrapper.le(fieldName,value);
                            break;
                        case "$null":
                            queryWrapper.isNull(fieldName);
                            break;
                        case "$notNull":
                            queryWrapper.isNotNull(fieldName);
                            break;
                        case "$in":
                            if(value instanceof  Collection)
                                queryWrapper.in(fieldName,(Collection)value);
                            else
                                queryWrapper.in(fieldName,value.toString().split(";|,|；|，"));
                            break;
                        case "$notIn":
                            if(value instanceof  Collection)
                                queryWrapper.notIn(fieldName,(Collection)value);
                            else
                                queryWrapper.notIn(fieldName,value.toString().split(";|,|；|，"));
                            break;
                        case "$like":
                            queryWrapper.like(fieldName,value);
                            break;
                        case "$startsWith":
                            queryWrapper.likeRight(fieldName,value);
                            break;
                        case "$endsWith":
                            queryWrapper.likeLeft(fieldName,value);
                            break;
                        case "$exists":
                            if(value!=null)
                                queryWrapper.exists(value.toString());
                            break;
                        case "$notExists":
                            if(value!=null)
                                queryWrapper.notExists(value.toString());
                            break;
                        case "$custom":
                            if(value!=null)
                                queryWrapper.and(query->query.apply(value.toString()));
                            break;
                    }
                }
            }
        };
        return consumer;
    }

    /**
     * 忽略未存在实体属性的搜索项
     * @param paramMap
     * @return
     */
    protected Map<String, Object> ignoreUnknownField(Map<String,Object> paramMap) {
        if(!ObjectUtils.isEmpty(paramMap)){
            List<String> searchItems = paramMap.keySet().stream().filter(e->e instanceof String && ((String)e).startsWith("n_")).collect(Collectors.toList());
            if(!ObjectUtils.isEmpty(searchItems)){
                List<String> unknownFields = new ArrayList<>();
                for(String searchItem : searchItems){
                    if(!existField(searchItem))
                        unknownFields.add(searchItem);
                }
                if(!ObjectUtils.isEmpty(unknownFields))
                    unknownFields.forEach(item-> paramMap.remove(item));
            }
        }
        return paramMap;
    }

    /**
     * 是否存在搜索项对应的实体属性
     * @param searchItem 搜索项名称
     * @return
     */
    protected boolean existField(String searchItem){
        int firstIndex = searchItem.indexOf("_");
        int lastIndex = searchItem.lastIndexOf("_");

        if (firstIndex != -1 && lastIndex != -1 && firstIndex != lastIndex) {
            String column = searchItem.substring(firstIndex + 1, lastIndex);
            return !ObjectUtils.isEmpty(getBeanSchema().getField(column).getFieldName());
        }
        return true;
    }
}
